/*
    ,--.                     ,--.     ,--.  ,--.
  ,-'  '-.,--.--.,--,--.,---.|  |,-.,-'  '-.`--' ,---. ,--,--,      Copyright 2024
  '-.  .-'|  .--' ,-.  | .--'|     /'-.  .-',--.| .-. ||      \   Tracktion Software
    |  |  |  |  \ '-'  \ `--.|  \  \  |  |  |  |' '-' '|  ||  |       Corporation
    `---' `--'   `--`--'`---'`--'`--' `---' `--' `---' `--''--'    www.tracktion.com

    Tracktion Engine uses a GPL/commercial licence - see LICENCE.md for details.
*/

namespace tracktion { inline namespace engine
{

//==============================================================================
/**
    SmartThumnail automatically tracks changes to an AudioFile and will update its
    cache if the file changes.
    Additionally, if this file is a wav proxy file that's being generated, you can
    use this to find out about its progress.
*/
class SmartThumbnail   : public juce::AudioThumbnailBase,
                         private juce::Timer
{
public:
    //==============================================================================
    /** Creates a SmartThumbnail for an AudioFile which will automatically repaint a
        Component as it it loaded.
        You can optionally supply an Edit to use its temporary directory for the
        thumbnail file storage.
    */
    SmartThumbnail (Engine&, const AudioFile&, juce::Component& componentToRepaint, Edit*);

    /** Creates a SmartThumbnail for an AudioFile which will automatically repaint a
        Component as it it loaded.
        You can optionally supply an Edit to use its temporary directory for the
        thumbnail file storage.
        @param thumbnailToUse   The thumbnail instance to use if you want custom thumbs
    */
    SmartThumbnail (Engine&, const AudioFile&, juce::Component& componentToRepaint, Edit*,
                    std::unique_ptr<juce::AudioThumbnailBase> thumbnailToUse);

    /** Destructor. */
    ~SmartThumbnail() override;

    /** Enable/disable all smart thumbnail generation. */
    static void setEnabled (bool e)             { enabled = e; }

    /** Returns true if any thumbnails are currently being generated for the given Edit. */
    static bool areThumbnailsFullyLoaded (Engine&);

    //==============================================================================
    /** Sets a new file to display. */
    void setNewFile (const AudioFile&);

    /** Returns true if this is a proxy file being generated by the AudioProxyGenerator. */
    bool isGeneratingProxy() const noexcept     { return wasGeneratingProxy; }

    /** Returns the progress of the current proxy file creation. */
    float getProxyProgress() const noexcept     { return lastProgress; }

    /** Returns true if the file has been changed and a new thumbnail has not yet been generated. */
    bool isOutOfDate() const noexcept           { return thumbnailIsInvalid; }

    //==============================================================================
    /** Draws one of the channels, optionally using a hi-res algorithm. */
    void drawChannel (juce::Graphics&, juce::Rectangle<int>,
                      TimeRange, int channelNum, float verticalZoomFactor);

    /** Draws all of the channels, optionally using a hi-res algorithm. */
    void drawChannels (juce::Graphics&, juce::Rectangle<int>,
                       TimeRange, float verticalZoomFactor);

    /** Returns the proportion of the thumbnail that has been generated. */
    double getProportionComplete() const noexcept;

    //==============================================================================
    /** @internal. */
    void clear() override;
    /** @internal. */
    bool setSource (juce::InputSource*) override;
    /** @internal. */
    void setReader (juce::AudioFormatReader*, juce::int64 hashCode) override;
    /** @internal. */
    bool loadFrom (juce::InputStream&) override;
    /** @internal. */
    void saveTo (juce::OutputStream&) const override;
    /** @internal. */
    int getNumChannels() const noexcept override;
    /** @internal. */
    double getTotalLength() const noexcept override;
    /** @internal. */
    bool isFullyLoaded() const noexcept override;
    /** @internal. */
    juce::int64 getNumSamplesFinished() const noexcept override;
    /** @internal. */
    float getApproximatePeak() const override;
    /** @internal. */
    void getApproximateMinMax (double startTime, double endTime, int channelIndex,
                               float& minValue, float& maxValue) const noexcept override;
    /** @internal. */
    juce::int64 getHashCode() const override;
    /** @internal. */
    void reset (int numChannels, double sampleRate, juce::int64 totalSamplesInSource) override;
    /** @internal. */
    void addBlock (juce::int64 sampleNumberInSource, const juce::AudioBuffer<float>&,
                   int startOffsetInBuffer, int numSamples) override;

    //==============================================================================
    /** @internal */
    void audioFileChanged();
    /** @internal */
    void releaseFile();

    //==============================================================================
    AudioFile file;
    Engine& engine;
    Edit* const edit = nullptr;

private:
    //==============================================================================
    std::unique_ptr<juce::AudioThumbnailBase> thumbnail;
    juce::Component& component;
    bool wasGeneratingProxy = false;
    std::atomic<bool> thumbnailIsInvalid { true };
    float lastProgress = 0.0f;

    void timerCallback() override;
    void fileWasChanged();
    void createThumbnailReader();

    static bool enabled;

    /** @internal. */
    void drawChannel (juce::Graphics&,
                      const juce::Rectangle<int>&,
                      double startTimeSeconds,
                      double endTimeSeconds,
                      int channelNum,
                      float verticalZoomFactor) override;
    /** @internal. */
    void drawChannels (juce::Graphics&,
                       const juce::Rectangle<int>&,
                       double startTimeSeconds,
                       double endTimeSeconds,
                       float verticalZoomFactor) override;

    JUCE_DECLARE_WEAK_REFERENCEABLE(SmartThumbnail)
    JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (SmartThumbnail)
};

}} // namespace tracktion { inline namespace engine
